package com.xiejr.aspect;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson.JSON;
import com.auth0.jwt.interfaces.Claim;
import com.xiejr.annotations.ApiSecurityIgnore;
import com.xiejr.cache.CacheStrategy;
import com.xiejr.context.ApplicationContextHolder;
import com.xiejr.enumeration.ResultEnum;
import com.xiejr.model.exception.BusinessException;
import com.xiejr.util.JwtUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

;

/**
 * @program: sophis-new
 * @description: 全局接口安全校验
 * @author: xjr
 * @create: 2020-12-05 21:12
 **/
@Aspect
@Configuration
@Slf4j
@Order(100)
@ConditionalOnProperty(prefix = "security",name = "enable",havingValue = "true")
public class GlobalSecurityAspect {

    @Autowired(required = false)
    CacheStrategy cacheStrategy;

    private static final String SIGN_SEC_KEY="sadaslkdjasdjaslkdjasldjasldjalsjdaskldasmndnaxmmznzcmsdaskdaksdjadlasdkasdasdhsahdasdasldsajdasldjasdaxjjsds";

    @Pointcut("execution(* com.xiejr.controller..*.*(..))")
    public void point(){

    }


    @Before("point()")
    public void before(JoinPoint joinPoint){
        ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        try {
            Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
            if (!method.isAnnotationPresent(ApiSecurityIgnore.class)){
                if (cacheStrategy==null){
                    throw new BusinessException("没有缓存组件,无法进行接口安全性校验");
                }
                check(request);
            }

        }catch (BusinessException ex){
           throw new BusinessException(ex.getResultEnum(),ex.getMessage());

        }
    }

    @After("point()")
    public void after(){
        ApplicationContextHolder.clear();
    }


    @SneakyThrows
    public void check(HttpServletRequest request){
        //判断token，token为空直接返回无权限
        String token = request.getHeader(ApplicationContextHolder.T);
        String sign=request.getHeader(ApplicationContextHolder.SIGN);
        String timestamp=request.getHeader(ApplicationContextHolder.TS);

        if (StrUtil.isBlank(token) || StrUtil.isBlank(sign) || StrUtil.isBlank(timestamp)){
            throw new BusinessException("非法请求");
        }
        Map<String, Claim> decode=null;
        try {
            decode = JwtUtil.decode(token);
        }catch (BusinessException ex){
            throw new BusinessException(ResultEnum.NO_AUTH,"无效令牌");
        }
        String uid=decode.get(JwtUtil.USER_ID).asString();
        checkToken(decode,uid);
        checkSign(request,sign);
        checkTs(timestamp);
        ApplicationContextHolder.set(ApplicationContextHolder.T, token);
        //校验通过后 刷新token有效时间延长30min
         cacheStrategy.set(uid,decode.get(JwtUtil.SALT).asString(),1800);


    }


    /**
     * 检查token
     * @param decode  解密后的jwt密钥信息
     */
    private void checkToken(Map<String,Claim> decode,String uid){
        //判断跟redis中是否存在此key
        if (StrUtil.isNotBlank(uid) ){
            boolean hasKey=cacheStrategy.hasKey(uid);
            if (hasKey){
                //判断客户端是否在异地登录
                String salt = cacheStrategy.get(uid).toString();
                if(!salt.equals(decode.get(JwtUtil.SALT).asString())){
                    throw  new BusinessException(ResultEnum.NO_AUTH,"你已在其他地点登录,请确认账号是否已被盗窃!");
                }
            }else{
                throw  new BusinessException(ResultEnum.NO_AUTH,"无效令牌");
            }

        }else{
            throw  new BusinessException(ResultEnum.NO_AUTH,"令牌校验不通过");
        }
    }

    /**
     * @param request 请求
     * @param sign 签名
     * @return void
     */
    public  void checkSign(HttpServletRequest request,String sign){
        Boolean flag= false;
        //检查sigin是否过期
        Enumeration<?> pNames =  request.getParameterNames();
        Map<String, String> params = new HashMap<String, String>();
        while (pNames.hasMoreElements()) {
            String pName = (String) pNames.nextElement();
            if("sign".equals(pName)) continue;
            String pValue = (String)request.getParameter(pName);
            params.put(pName, pValue);
        }
        if(!sign.toLowerCase().equals(getSign(params))){
            throw new BusinessException(ResultEnum.NO_AUTH,"参数已被修改！！");
        }

    }


    private void checkTs(String timestamp){
        long l = Long.parseLong(timestamp);
        long c=System.currentTimeMillis();
        if ((c-l)>10000){
            throw new BusinessException(ResultEnum.NO_AUTH,"过期请求");
        }
    }


    /**
     * 生成签名
     * @return
     */
    private String getSign(Map<String,String> params){
        return DigestUtil.md5Hex(JSON.toJSONString(params)+SIGN_SEC_KEY, "UTF-8");
    }




}
